#include <QMutex>
#include <QUdpSocket>
#include <execinfo.h>
#include <dlfcn.h>
#include <cxxabi.h>

/* TODO
 - threading support (now it messes results)
 - 64 bit support
 - different endianess support
*/

#define DELAY_EXECUTION 1000

// Global Operation State
static bool __cEnabled = false;

// Per-Thread context
struct _CrystalThreadContext {
    QMutex      noRecursiveMutex;
    FILE *      file;
    QUdpSocket *udp;
    char        sendBuffer[ 500 ];

    qint64      statsAllocMin;
    qint64      statsAllocMax;
    qint64      statsAllocSize;

    _CrystalThreadContext()
        : file( 0 )
        , statsAllocMin( 0 )
        , statsAllocMax( 0 )
        , statsAllocSize( 0 )
    {
        // TODO: open file here?
    }
    ~_CrystalThreadContext()
    {
        // TODO: close file here?
    }

    inline void updateStats( int value ) {
        statsAllocSize += value;
        if ( statsAllocSize < statsAllocMin )
        statsAllocMin = statsAllocSize;
        if ( statsAllocSize > statsAllocMax )
        statsAllocMax = statsAllocSize;
    }

    inline void printStats() {
        fprintf( stderr, "allocation: top: %lld current: %lld\n", statsAllocMax, statsAllocSize );
    }
};

static _CrystalThreadContext __cCtx1;


// BACKTRACE GENERIC FUNCTION
#define BACKTRACE_MAXDEPTH 32

// con inline rimuovo 1 livello di stack!
inline void __printBacktrace( int skipCount )
{
    void * func_addresses[ BACKTRACE_MAXDEPTH ];
    char demangle_output[ 1000 ];
    int nfuncs = backtrace( func_addresses, BACKTRACE_MAXDEPTH );
    for ( int i = skipCount; i < nfuncs; ++i ) {
        Dl_info info;
        if ( dladdr( func_addresses[i], &info ) ) {
            int dStat;
            size_t dLength = 1000;
            char * demangled = abi::__cxa_demangle(info.dli_sname, demangle_output, &dLength, &dStat);
            if ( demangled && !dStat )
                fprintf(stderr, "return addr: %p: object:%s %p symbol:%s [%p+0x%x]\n", func_addresses[i], info.dli_fname, info.dli_fbase, demangled, info.dli_saddr, (int)func_addresses[i] - (int)info.dli_saddr);
            else
                fprintf(stderr, "return addr: %p: object:%s %p symbol:%s [%p+0x%x]\n", func_addresses[i], info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr, (int)func_addresses[i] - (int)info.dli_saddr);
        } else
            fprintf(stderr, "return addr: %p\n", func_addresses[i]);
    }
}

// SERIALIZATION FUNCTION TO FILE
//#define DISPLAY_SERIALIZATION_STRING
inline void __sendNew( int skipCount, quint8 type, quint32 size, void *addr )
{
    if ( !__cCtx1.file ) {
        fprintf(stderr, "__sendNew: file not open!\n");
        return;
    }

    // header
#ifdef DISPLAY_SERIALIZATION_STRING
    fprintf(stderr, "%d%08x%08x", type, size, addr);
#endif
    int headerIdx = 0;
    __cCtx1.sendBuffer[ headerIdx++ ] = type;
    memcpy( __cCtx1.sendBuffer + headerIdx, &size, sizeof(quint32) );
    headerIdx += sizeof(quint32);
    memcpy( __cCtx1.sendBuffer + headerIdx, &addr, sizeof(quint32) );
    headerIdx += sizeof(quint32);

    // for each stack trace add an entry
    void * func_addresses[ BACKTRACE_MAXDEPTH ];
    int nfuncs = backtrace( func_addresses, BACKTRACE_MAXDEPTH );
    for ( int i = 0; i < nfuncs; ++i ) {
        Dl_info info;
        if ( !dladdr( func_addresses[i], &info ) )
            continue;
#ifdef DISPLAY_SERIALIZATION_STRING
        fprintf(stderr, ";%08x%08x%08x", info.dli_fbase, info.dli_saddr, func_addresses[i]);
#endif
        __cCtx1.sendBuffer[ headerIdx++ ] = ';';
        memcpy( __cCtx1.sendBuffer + headerIdx, &(info.dli_fbase), sizeof(quint32) );
        headerIdx += sizeof(quint32);
        memcpy( __cCtx1.sendBuffer + headerIdx, &(info.dli_saddr), sizeof(quint32) );
        headerIdx += sizeof(quint32);
        memcpy( __cCtx1.sendBuffer + headerIdx, &(func_addresses[i]), sizeof(quint32) );
        headerIdx += sizeof(quint32);
    }

    // write closure
#ifdef DISPLAY_SERIALIZATION_STRING
    fprintf(stderr, ".\n");
#endif
    __cCtx1.sendBuffer[ headerIdx++ ] = '.';
    fwrite( __cCtx1.sendBuffer, headerIdx, 1, __cCtx1.file );
    __cCtx1.udp->writeDatagram( (const char*)__cCtx1.sendBuffer, headerIdx, QHostAddress::LocalHost, 1337 );
    usleep( DELAY_EXECUTION );
}

inline void __sendDelete( quint8 type, void *addr )
{
    if ( !__cCtx1.file ) {
        fprintf(stderr, "__sendDelete: file not open!\n");
        return;
    }

    // header
#ifdef DISPLAY_SERIALIZATION_STRING
    fprintf(stderr, "%d%08x", type, addr);
#endif
    int headerIdx = 0;
    __cCtx1.sendBuffer[ headerIdx++ ] = type;
    memcpy( __cCtx1.sendBuffer + headerIdx, &addr, sizeof(quint32) );
    headerIdx += sizeof(quint32);
    // write closure
#ifdef DISPLAY_SERIALIZATION_STRING
    fprintf(stderr, ".\n");
#endif
    __cCtx1.sendBuffer[ headerIdx++ ] = '.';
    fwrite( __cCtx1.sendBuffer, headerIdx, 1, __cCtx1.file );
    __cCtx1.udp->writeDatagram( (const char*)__cCtx1.sendBuffer, headerIdx, QHostAddress::LocalHost, 1337 );
    usleep( DELAY_EXECUTION );
}

//BEGIN Operator Hooks
#define CRYSTALMEM_OPERATOR_LOCK( desc, size, addr, code ) \
    if ( __cEnabled ) { \
        if ( __cCtx1.noRecursiveMutex.tryLock() ) { \
            __cCtx1.updateStats( size ); \
            /*fprintf( stderr, desc, size, addr );*/ \
            code \
            __cCtx1.noRecursiveMutex.unlock(); \
        } \
    }

void *operator new[]( size_t size )
{
    void *ret = malloc( size );
    CRYSTALMEM_OPERATOR_LOCK( "* new[]( %i ) %p\n", size, ret,
        //__printBacktrace(1);
        __sendNew( 1, 2, size, ret );
    )
    return ret;
}

void *operator new( size_t size )
{
    void *ret = malloc( size );
    CRYSTALMEM_OPERATOR_LOCK( "* new( %i ) %p\n", size, ret,
        //__printBacktrace(1);
        __sendNew( 1, 1, size, ret );
    )
    return ret;
}

void operator delete[](void * ptr)
{
    CRYSTALMEM_OPERATOR_LOCK( "delete[]( %i ) %p\n", 0 /*~sizes[ ptr ]*/, ptr,
        //__printBacktrace(1);
        __sendDelete( 3, ptr );
    )
    free( ptr );
}

void operator delete[](void * ptr, size_t)
{
    CRYSTALMEM_OPERATOR_LOCK( "delete[]( *, %i ) %p\n", 0 /*~sizes[ ptr ]*/, ptr,
        //__printBacktrace(1);
        __sendDelete( 4, ptr );
    )
    free( ptr );
}

void operator delete(void * ptr)
{
    CRYSTALMEM_OPERATOR_LOCK( "delete( %i ) %p\n", 0 /*~sizes[ ptr ]*/, ptr,
        //__printBacktrace(1);
        __sendDelete( 5, ptr );
    )
    free( ptr );
}

void operator delete(void * ptr, size_t)
{
    CRYSTALMEM_OPERATOR_LOCK( "delete( *, %i ) %p\n", 0 /*~sizes[ ptr ]*/, ptr,
        //__printBacktrace(1);
        __sendDelete( 6, ptr );
    )
    free( ptr );
}
//END Operator Hooks



inline void enableCrystalMem() {
    //__cGlobal.mutex.lock();
    if ( __cEnabled ) {
        fprintf( stderr, "enableCrystalMem: Crystal Mem already enabled\n" );
    } else {
        // TODO create sockets/files...
        // FIXME: not per thread? or yes?
        __cCtx1.file = fopen( "output", "w+" );
	__cCtx1.udp = new QUdpSocket;

        // start operation
        __cEnabled = true;

        fprintf( stderr, "> Crystal Mem enabled\n" );
    }
    //__cGlobal.mutex.unlock();
}

inline void disableCrystalMem() {
    //__cGlobal.mutex.lock();
    if ( !__cEnabled ) {
        fprintf( stderr, "disableCrystalMem: Crystal Mem already disabled\n" );
    } else {
        // stop operation
        __cEnabled = false;

        // TODO close files
        // FIXME: not per thread? or yes?
        if ( __cCtx1.file ) {
            fclose( __cCtx1.file );
            __cCtx1.file = 0;
        }
	delete __cCtx1.udp;

        fprintf( stderr, "> Crystal Mem disabled\n" );
    }
    //__cGlobal.mutex.unlock();
}

#define CRYSTALMEM_PROBE( x ) { enableCrystalMem(); { x; } disableCrystalMem(); }

